* enhance exif subsecond and offset handling.
* don't warn for legal empty offset tags.
}
QDateTime
-ExifFormat::exif_get_exif_time(ExifApp* app)
+ExifFormat::exif_get_exif_time(ExifApp* app) const
{
QDateTime res;
// Exif 2.31 added offset tags to record the offset to UTC.
// If these are present use them, otherwise assume local time.
ExifTag* offset_tag = nullptr;
+ ExifTag* subsec_tag = nullptr;
switch (tag->id) {
case 0x9003:
offset_tag = exif_find_tag(app, EXIF_IFD, 0x9011); /* OffsetTimeOriginal from EXIF */
+ subsec_tag = exif_find_tag(app, EXIF_IFD, 0x9291); /* SubSecTimeOriginal from EXIF */
break;
case 0x0132:
offset_tag = exif_find_tag(app, EXIF_IFD, 0x9010); /* OffsetTime from EXIF */
+ subsec_tag = exif_find_tag(app, EXIF_IFD, 0x9290); /* SubSecTime from EXIF */
break;
case 0x9004:
offset_tag = exif_find_tag(app, EXIF_IFD, 0x9012); /* OffsetTimeDigitized from EXIF */
+ subsec_tag = exif_find_tag(app, EXIF_IFD, 0x9292); /* SubSecTimeDigitized from EXIF */
break;
}
- if (offset_tag) {
- QByteArray time_tag = exif_read_str(offset_tag);
+ if (offset_tag || opt_offsettime) {
+ QByteArray time_tag = opt_offsettime? QByteArray(opt_offsettime) : exif_read_str(offset_tag);
// string should be +HH:MM or -HH:MM
static const QRegularExpression re(R"(^([+-])(\d{2}):(\d{2})$)");
assert(re.isValid());
int offset_hours = match.captured(1).append(match.captured(2)).toInt();
int offset_mins = match.captured(1).append(match.captured(3)).toInt();
res.setOffsetFromUtc(((offset_hours * 60) + offset_mins) * 60);
+ } else if (opt_offsettime) {
+ // Only warn for user supplied offsets.
+ // Offset tags may indicate the offset was unknown, e.g. " : ".
+ warning(MYNAME ": OffsetTime is expected to be +HH:MM or -HH:MM, but was %s.\n", time_tag.constData());
}
}
+
+ if (subsec_tag) {
+ QByteArray subsec = exif_read_str(subsec_tag);
+ bool ok;
+ double ss = subsec.prepend("0.").toDouble(&ok);
+ if (ok) {
+ res = res.addMSecs(lround(1000.0 * ss));
+ }
+ }
+
}
- return res;
+ return res.toUTC();
}
Waypoint*
gps_datetime = QDateTime(datestamp, timestamp, Qt::UTC);
if (gps_datetime.isValid()) {
if (global_opts.debug_level >= 3) {
- printf(MYNAME "-GPSTimeStamp = %s\n", qPrintable(gps_datetime.toString(Qt::ISODate)));
+ printf(MYNAME "-GPSTimeStamp = %s\n", qPrintable(gps_datetime.toString(Qt::ISODateWithMs)));
}
wpt->SetCreationTime(gps_datetime);
} else {
exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 0, dt.time().hour());
exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 1, dt.time().minute());
- exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 2, dt.time().second());
+ exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 2,
+ static_cast<double>(dt.time().second()) +
+ static_cast<double>(dt.time().msec())/1000.0);
exif_put_str(GPS_IFD, GPS_IFD_TAG_DATESTAMP, CSTR(dt.toString(u"yyyy:MM:dd")));
} else {
static void exif_examine_app(ExifApp* app);
static ExifIfd* exif_find_ifd(ExifApp* app, uint16_t ifd_nr);
static ExifTag* exif_find_tag(ExifApp* app, uint16_t ifd_nr, uint16_t tag_id);
- static QDateTime exif_get_exif_time(ExifApp* app);
+ QDateTime exif_get_exif_time(ExifApp* app) const;
Waypoint* exif_waypt_from_exif_app(ExifApp* app) const;
static Rational<int> exif_dec2frac(double val, double tolerance);
ExifTag* exif_put_value(int ifd_nr, uint16_t tag_id, uint16_t type, int count, int index, const void* data) const;
char* opt_overwrite{};
char* opt_frame{};
char* opt_name{};
+ char* opt_offsettime{};
QVector<arglist_t> exif_args = {
{ "filename", &opt_filename, "Set waypoint name to source filename", "Y", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr },
{ "frame", &opt_frame, "Time-frame (in seconds)", "10", ARGTYPE_INT, "0", nullptr, nullptr },
{ "name", &opt_name, "Locate waypoint for tagging by this name", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
{ "overwrite", &opt_overwrite, "!OVERWRITE! the original file. Default=N", "N", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr },
+ { "offset", &opt_offsettime, "Image Offset Time (+HH:MM or -HH:MM)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
};
};
#endif // EXIF_H_INCLUDED_
option exif overwrite !OVERWRITE! the original file. Default=N boolean N https://www.gpsbabel.org/WEB_DOC_DIR/fmt_exif.html#fmt_exif_o_overwrite
+option exif offset Image Offset Time (+HH:MM or -HH:MM) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_exif.html#fmt_exif_o_offset
+
file rwrwrw shape shp ESRI shapefile shape
https://www.gpsbabel.org/WEB_DOC_DIR/fmt_shape.html
option shape name Source for name field in .dbf string 0 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_shape.html#fmt_shape_o_name
frame Time-frame (in seconds)
name Locate waypoint for tagging by this name
overwrite (0/1) !OVERWRITE! the original file. Default=N
+ offset Image Offset Time (+HH:MM or -HH:MM)
shape ESRI shapefile
name Source for name field in .dbf
url Source for URL field in .dbf
<userinput>gpsbabel -i gpx -f holiday.gpx -o exif,frame=60 -F IMG0784.JPG</userinput>
</para>
<para>
- If the camera time wasn't adjusted, you should move the track(s) by the this difference.
- I.e. if the camera time is five minutes behind your time, the track(s) should be shifted
- five minutes back.
-</para>
-<para>
- <userinput>gpsbabel -i gpx -f holiday.gpx -x track,move=-5m -o exif,frame=60 -F IMG0784.JPG</userinput>
+ If the camera time wasn't adjusted, you should use the offset option. You may also need to use the frame option, or
+ the interpolate filter.
</para>
--- /dev/null
+<para>
+ Uses the given value instead of the value from the tag OffsetTime, OffsetTimeOriginal or OffsetTimeDigitized.
+ This is useful when the image doesn't contain an OffsetTime* tag and the offset is different from the local time, or when the image contains a tag that is incorrect.
+ The format of this option should match that of the tag OffsetTime*, specifcally it must be "+HH:MM" or "-HH:MM".
+</para>
+<para>
+ If the camera was using China Standard Time, e.g. in the winter in Taiwan, then you should supply
+ an offset of "+8:00".
+<userinput>gpsbabel -i gpx -f holiday.gpx -o exif,offset=+08:00 -F IMG0784.JPG</userinput>
+</para>